/*
 * Copyright Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pub.devrel.easypermissions;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.fraction.Fraction;
import ohos.app.AbilityContext;
import ohos.bundle.IBundleManager;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import pub.devrel.easypermissions.helper.PermissionHelper;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Utility to request and check System permissions for apps.
 */
public class EasyPermissions {
    /**
     * Callback interface to receive the results of {@code EasyPermissions.requestPermissions()}
     * calls.
     */
    public interface PermissionCallbacks {
        void onPermissionsGranted(int requestCode, List<String> perms);

        void onPermissionsDenied(int requestCode, List<String> perms);
    }

    /**
     * Callback interface to receive button clicked events of the rationale dialog
     */
    public interface RationaleCallbacks {
        void onRationaleAccepted(int requestCode);

        void onRationaleDenied(int requestCode);
    }

    private static final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");

    /**
     * Check if the calling context has a set of permissions.
     *
     * @param context the calling context.
     * @param perms one ore more permissions, such as {@link }.
     * @return true if all permissions are already granted, false if at least one permission is not
     * yet granted.
     * @see
     */
    public static boolean hasPermissions(AbilityContext context, String... perms) {
        // Null context may be passed if we have detected Low API (less than M) so getting
        // to this point with a null context should not be possible.
        if (context == null) {
            throw new IllegalArgumentException("Can't check permissions for null context");
        }

        for (String perm : perms) {
            HiLog.info(TAG, "hasPermissions : " + context.verifySelfPermission(perm));
            if (context.verifySelfPermission(perm) != IBundleManager.PERMISSION_GRANTED) {
                return false;
            }
        }

        return true;
    }

    /**
     * Request a set of permissions, showing a rationale if the system requests it.
     *
     * @param host requesting context.
     * @param rationale a message explaining why the application needs this set of permissions;
     * will be displayed if the user rejects the request the first time.
     * @param requestCode request code to track this request, must be &lt; 256.
     * @param perms a set of permissions to be requested.
     * @see
     */
    public static void requestPermissions(Ability host, String rationale,
                                          int requestCode, String... perms) {
        requestPermissions(
            new PermissionRequest.Builder(host, requestCode, perms)
                .setRationale(rationale)
                .build());
    }

    /**
     * Request permissions from a Support Fragment with standard OK/Cancel buttons.
     *
     * @param host requesting context.
     * @param rationale a message explaining why the application needs this set of permissions;
     * will be displayed if the user rejects the request the first time.
     * @param requestCode request code to track this request, must be &lt; 256.
     * @param perms a set of permissions to be requested.
     * @see #requestPermissions(Fraction, String, int, String...)
     */
    public static void requestPermissions(Fraction host, String rationale, int requestCode, String... perms) {
        requestPermissions(
            new PermissionRequest.Builder(host, requestCode, perms)
                .setRationale(rationale)
                .build());
    }

    /**
     * Request a set of permissions.
     *
     * @param request the permission request
     * @see PermissionRequest
     */
    public static void requestPermissions(PermissionRequest request) {
        // Check for permissions before dispatching the request
        if (hasPermissions(request.getHelper().getContext(), request.getPerms())) {
            notifyAlreadyHasPermissions(request.getHelper().getHost(), request.getRequestCode(), request.getPerms());
            return;
        }
        // Request permissions
        request.getHelper().requestPermissions(
            request.getRationale(),
            request.getPositiveButtonText(),
            request.getNegativeButtonText(),
            request.getTheme(),
            request.getRequestCode(),
            request.getPerms());
    }

    /**
     * Handle the result of a permission request, should be called from the calling {@link
     * Ability{@link AbilityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int,
     * String[], int[])} method.
     * <p>
     * If any permissions were granted or denied, the {@code object} will receive the appropriate
     * callbacks through {@link PermissionCallbacks} and methods annotated with {@link
     * AfterPermissionGranted} will be run if appropriate.
     *
     * @param requestCode requestCode argument to permission result callback.
     * @param permissions permissions argument to permission result callback.
     * @param grantResults grantResults argument to permission result callback.
     * @param receivers an array of objects that have a method annotated with {@link
     * AfterPermissionGranted} or implement {@link PermissionCallbacks}.
     */
    public static void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults, Object... receivers) {
        // Make a collection of granted and denied permissions from the request.
        HiLog.info(TAG, "onRequestPermissionsResult进来了");
        List<String> granted = new ArrayList<>();
        List<String> denied = new ArrayList<>();
        for (int i = 0; i < permissions.length; i++) {
            String perm = permissions[i];
            HiLog.info(TAG, "perm = " + perm);
            HiLog.info(TAG, "perm = " + grantResults[i]);
            if (grantResults[i] == IBundleManager.PERMISSION_GRANTED) {
                granted.add(perm);
            } else {
                denied.add(perm);
            }
        }

        // iterate through all receivers
        for (Object object : receivers) {
            // Report denied permissions, if any.
            if (!denied.isEmpty()) {
                if (object instanceof PermissionCallbacks) {
                    ((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied);
                    return;
                }
            }

            // Report granted permissions, if any.
            if (!granted.isEmpty()) {
                HiLog.info(TAG, "object instanceof PermissionCallbacks : " + (object instanceof PermissionCallbacks));
                if (object instanceof PermissionCallbacks) {
                    ((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted);
                }
            }

            HiLog.info(TAG, "granted size = " + (granted.isEmpty() ? 0 : granted.size()));
            HiLog.info(TAG, "denied size = " + (denied.isEmpty() ? 0 : granted.size()));
            // If 100% successful, call annotated methods
            if (!granted.isEmpty() && denied.isEmpty()) {
                HiLog.info(TAG, "注解调用");
                runAnnotatedMethods(object, requestCode);
            }
        }
    }

    /**
     * Check if at least one permission in the list of denied permissions has been permanently
     * denied (user clicked "Never ask again").
     *
     * <b>Note</b>: Due to a limitation in the information provided
     * framework permissions API, this method only works after the permission
     * has been denied and your app has received the onPermissionsDenied callback.
     * Otherwise the library cannot distinguish permanent denial from the
     * "not yet denied" case.
     *
     * @param host context requesting permissions.
     * @param deniedPermissions list of denied permissions, usually from {@link
     * PermissionCallbacks#onPermissionsDenied(int, List)}
     * @return {@code true} if at least one permission in the list was permanently denied.
     */
    public static boolean somePermissionPermanentlyDenied(Ability host, List<String> deniedPermissions) {
        return PermissionHelper.newInstance(host)
            .somePermissionPermanentlyDenied(deniedPermissions);
    }

    /**
     * Check if at least one permission in the list of denied permissions has been permanently
     *
     * @param host 容器类型
     * @param deniedPermissions 被拒绝的权限
     * @return true 有被拒绝的权限 反之返回false
     * @see #somePermissionPermanentlyDenied(Fraction, List)
     */
    public static boolean somePermissionPermanentlyDenied(Fraction host, List<String> deniedPermissions) {
        return PermissionHelper.newInstance(host)
            .somePermissionPermanentlyDenied(deniedPermissions);
    }

    /**
     * Check if a permission has been permanently denied (user clicked "Never ask again").
     *
     * @param host context requesting permissions.
     * @param deniedPermission denied permission.
     * @return {@code true} if the permissions has been permanently denied.
     */
    public static boolean permissionPermanentlyDenied(Ability host, String deniedPermission) {
        return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission);
    }

    /**
     * Check if a permission has been permanently denied
     *
     * @param host context requesting permissions.
     * @param deniedPermission denied permission.
     * @return {@code true} if the permissions has been permanently denied.
     * @see #permissionPermanentlyDenied(Fraction, String)
     */
    public static boolean permissionPermanentlyDenied(Fraction host, String deniedPermission) {
        return PermissionHelper.newInstance(host).permissionPermanentlyDenied(deniedPermission);
    }

    /**
     * See if some denied permission has been permanently denied.
     *
     * @param host requesting context.
     * @param perms array of permissions.
     * @return true if the user has previously denied any of the {@code perms} and we should show a
     * rationale, false otherwise.
     */
    public static boolean somePermissionDenied(Ability host, String... perms) {
        return PermissionHelper.newInstance(host).somePermissionDenied(perms);
    }

    /**
     * See if some denied permission has been permanently denied.
     *
     * @param host requesting context.
     * @param perms array of permissions.
     * @return true if the user has previously denied any of the {@code perms} and we should show a
     * rationale, false otherwise.
     * @see #somePermissionDenied(Fraction, String...)
     */
    public static boolean somePermissionDenied(Fraction host, String... perms) {
        return PermissionHelper.newInstance(host).somePermissionDenied(perms);
    }

    /**
     * Run permission callbacks on an object that requested permissions but already has them by
     * simulating {@link }.
     *
     * @param object the object requesting permissions.
     * @param requestCode the permission request code.
     * @param perms a list of permissions requested.
     */
    private static void notifyAlreadyHasPermissions(Object object, int requestCode, String[] perms) {
        int[] grantResults = new int[perms.length];
        for (int i = 0; i < perms.length; i++) {
            grantResults[i] = IBundleManager.PERMISSION_GRANTED;
        }

        onRequestPermissionsResult(requestCode, perms, grantResults, object);
    }

    /**
     * Find all methods annotated with {@link AfterPermissionGranted} on a given object with the
     * correct requestCode argument.
     *
     * @param object the object with annotated methods.
     * @param requestCode the requestCode passed to the annotation.
     */
    private static void runAnnotatedMethods(Object object, int requestCode) {
        Class clazz = object.getClass();
        if (isUsingAnnotations(object)) {
            clazz = clazz.getSuperclass();
        }

        while (clazz != null) {
            for (Method method : clazz.getDeclaredMethods()) {
                AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
                if (ann != null) {
                    // Check for annotated methods with matching request code.
                    if (ann.value() == requestCode) {
                        // Method must be void so that we can invoke it
                        if (method.getParameterTypes().length > 0) {
                            throw new RuntimeException("Cannot execute method " + method.getName()
                                + " because it is non-void method and/or has input parameters.");
                        }

                        try {
                            // Make method accessible if private
                            if (!method.isAccessible()) {
                                HiLog.info(TAG, "runAnnotatedMethods");
                                method.setAccessible(true);
                            }
                            method.invoke(object);
                        } catch (IllegalAccessException e) {
                            HiLog.error(TAG, "runDefaultMethod:IllegalAccessException", e);
                        } catch (InvocationTargetException e) {
                            HiLog.error(TAG, "runDefaultMethod:InvocationTargetException", e);
                        }
                    }
                }
            }

            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Determine if the project is using the Annotations library.
     *
     * @param object the object with annotated methods.
     * @return true 使用 false 未使用
     */
    private static boolean isUsingAnnotations(Object object) {
        if (!object.getClass().getSimpleName().endsWith("_")) {
            return false;
        }
        try {
            // ohos.agp.components.element.FrameAnimationElement
            Class clazz = Class.forName("ohos.agp.components.element.FrameAnimationElement");
            return clazz.isInstance(object);
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}
